Caller-sensitive methods are a classification of methods that are able to determine some aspect of the context of the calling instruction from which they were invoked. By contrast, a caller-insensitive method can vary its behavior depending on the argument values passed to it by its caller, but its behavior does not depend on the identity of its caller. A caller-sensitive method can therefore change behavior depending on where the method is called from. Thus, an invocation of a caller-sensitive method from two different sources (e.g. different methods, classes, etc.) can have different results, even if the arguments passed into the caller-sensitive method are identical. For example, in the context of the Java programming language, each class is loaded with a particular Class Loader that determines the namespace utilized by the class. If a method is defined which returns an instance of a class corresponding to a particular name (e.g. passed in as a String argument), the class instantiation returned by the method could be completely different depending on the Class Loader who loaded the caller's class. For instance, the name “Address” in the context of a first Class Loader could refer to a class with fields for street address and zip code, whereas in the context of a second Class Loader “Address” could refer to a class that contains a field representing a memory address. Handling caller-sensitive methods, specifically determining the identity of the caller, involves tradeoffs between security and efficiency.
In one approach, the caller is trusted to supply, to the called method, an additional argument that identifies the caller. This approach is efficient since the identity of the caller is immediately known without many (or any) additional steps being performed to determine the identity of the caller. However, trusting the caller to accurately identify themselves opens up a wide variety of security concerns. For example, an attacker could forge the identity to make the call appear to come from a different source that has a different set of access permissions. As a result, the attacker could potentially obtain private information or capabilities that the attacker would ordinarily be barred from accessing. Thus, trusting the caller to provide their own identity is highly efficient, but that efficiency comes at the expense of security. However, in cases where the caller can be implicitly trusted and security is not a concern, this approach is quick and efficient to implement.
In another approach, the callee or an intermediary (such as a virtual machine controlling the execution) performs a stack walk to scan the data structure maintaining the execution of the program to determine the identity of the caller. For example, in some environments such as the Java Virtual Machine (JVM), each execution of a method pushes a stack frame onto a stack. As a result, the virtual machine can analyze the stack to determine which class is responsible for the previous stack frame. Stack walking is an expensive operation which would have to be performed each time the caller-sensitive method is executed. However, provided that the operating architecture protects the integrity of the stack, the identity of the caller can be accurately determined even if that caller is not trusted. Furthermore, stack walking enables the determination of further ancestors than just the parent caller since that information can be gleaned by performing a deeper inspection of the stack. For example, stack walking allows a trace to be performed even back to the first method executed in the program. Thus, compared to the previously described approach, this approach sacrifices a measure of efficiency for the sake of security and a more robust set of capabilities.